/*-------------------------------------------------------------------------
 *
 * basebackup_gzip.c
 *	  基备份接收器实现 gzip 压缩。
 *
 * Portions Copyright (c) 2010-2022, PostgreSQL Global Development Group
 *
 * IDENTIFICATION
 *	  src/backend/backup/basebackup_gzip.c
 *
 *-------------------------------------------------------------------------
 */
#include "postgres.h"

#ifdef HAVE_LIBZ
#include <zlib.h>
#endif

#include "backup/basebackup_sink.h"

#ifdef HAVE_LIBZ
typedef struct bbsink_gzip
{
	/* 所有类型的接收器的通用信息。 */
	bbsink		base;

	/* 压缩级别。 */
	int			compresslevel;

	/* 压缩数据流。 */
	z_stream	zstream;

	/* 输出缓冲区中分配的字节数。 */
	size_t		bytes_written;
} bbsink_gzip;

static void fc_bbsink_gzip_begin_backup(bbsink *fc_sink);
static void fc_bbsink_gzip_begin_archive(bbsink *fc_sink, const char *fc_archive_name);
static void fc_bbsink_gzip_archive_contents(bbsink *fc_sink, size_t fc_len);
static void fc_bbsink_gzip_manifest_contents(bbsink *fc_sink, size_t fc_len);
static void fc_bbsink_gzip_end_archive(bbsink *fc_sink);
static void *fc_gzip_palloc(void *fc_opaque, unsigned fc_items, unsigned fc_size);
static void fc_gzip_pfree(void *fc_opaque, void *fc_address);

static const bbsink_ops bbsink_gzip_ops = {
	.begin_backup = fc_bbsink_gzip_begin_backup,
	.begin_archive = fc_bbsink_gzip_begin_archive,
	.archive_contents = fc_bbsink_gzip_archive_contents,
	.end_archive = fc_bbsink_gzip_end_archive,
	.begin_manifest = bbsink_forward_begin_manifest,
	.manifest_contents = fc_bbsink_gzip_manifest_contents,
	.end_manifest = bbsink_forward_end_manifest,
	.end_backup = bbsink_forward_end_backup,
	.cleanup = bbsink_forward_cleanup
};
#endif

/*
 * 创建一个新的基本备份接收器，该接收器执行gzip压缩。
 */
bbsink * bbsink_gzip_new(bbsink *fc_next, pg_compress_specification *fc_compress)
{
#ifndef HAVE_LIBZ
	ereport(ERROR,
			(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
			 errmsg("gzip compression is not supported by this build")));
	return NULL;				/* 保持编译器安静 */
#else
	bbsink_gzip *fc_sink;
	int			fc_compresslevel;

	Assert(fc_next != NULL);

	fc_compresslevel = fc_compress->level;
	Assert((fc_compresslevel >= 1 && fc_compresslevel <= 9) ||
		   fc_compresslevel == Z_DEFAULT_COMPRESSION);

	fc_sink = palloc0(sizeof(bbsink_gzip));
	*((const bbsink_ops **) &fc_sink->base.bbs_ops) = &bbsink_gzip_ops;
	fc_sink->base.bbs_next = fc_next;
	fc_sink->compresslevel = fc_compresslevel;

	return &fc_sink->base;
#endif
}

#ifdef HAVE_LIBZ

/*
 * 开始备份。
 */
static void fc_bbsink_gzip_begin_backup(bbsink *fc_sink)
{
	/*
	 * 我们需要自己的缓冲区，因为我们将要传递给下一个接收器的数据
	 * 与传递给我们的数据不同。
	 */
	fc_sink->bbs_buffer = palloc(fc_sink->bbs_buffer_length);

	/*
	 * 由于deflate()不要求输出缓冲区具有任何特定大小，因此
	 * 我们可以将其大小设为与输入缓冲区相同。
	 */
	bbsink_begin_backup(fc_sink->bbs_next, fc_sink->bbs_state,
						fc_sink->bbs_buffer_length);
}

/*
 * 准备压缩下一个归档。
 */
static void fc_bbsink_gzip_begin_archive(bbsink *fc_sink, const char *fc_archive_name)
{
	bbsink_gzip *fc_mysink = (bbsink_gzip *) fc_sink;
	char	   *fc_gz_archive_name;
	z_stream   *fc_zs = &fc_mysink->zstream;

	/* 初始化压缩对象。 */
	memset(fc_zs, 0, sizeof(z_stream));
	fc_zs->zalloc = fc_gzip_palloc;
	fc_zs->zfree = fc_gzip_pfree;
	fc_zs->next_out = (uint8 *) fc_sink->bbs_next->bbs_buffer;
	fc_zs->avail_out = fc_sink->bbs_next->bbs_buffer_length;

	/*
	 * 我们需要使用deflateInit2()而不是deflateInit()，这样我们
	 * 才能请求gzip头而不是zlib头。否则，我们希望提供默认情况下
	 * 使用的相同值，如果我们只是调用deflateInit()。
	 *
	 * 根据deflateInit2的文档，第三个参数必须是Z_DEFLATED；
	 * 第四个参数是“窗口位”的数量，默认是15，但添加16将
	 * 获取gzip头而不是zlib头；第五个参数控制内存使用，8
	 * 是默认值；同样，Z_DEFAULT_STRATEGY是第六个参数的默认值。
	 */
	if (deflateInit2(fc_zs, fc_mysink->compresslevel, Z_DEFLATED, 15 + 16, 8,
					 Z_DEFAULT_STRATEGY) != Z_OK)
		ereport(ERROR,
				errcode(ERRCODE_INTERNAL_ERROR),
				errmsg("could not initialize compression library"));

	/*
	 * 在归档名称中添加“.gz”。请注意，pg_basebackup -z生成
	 * 名为“.tar.gz”的归档，而不是“.tgz”，因此我们在这里
	 * 匹配这一点。
	 */
	fc_gz_archive_name = psprintf("%s.gz", fc_archive_name);
	Assert(fc_sink->bbs_next != NULL);
	bbsink_begin_archive(fc_sink->bbs_next, fc_gz_archive_name);
	pfree(fc_gz_archive_name);
}

/*
 * 将输入数据压缩到输出缓冲区，直到输入数据用尽。
 * 每次输出缓冲区填满时，调用archive_contents()
 * 方法来处理下一个接收器。
 *
 * 请注意，由于我们正在压缩输入，可能会非常常见地发生
 * 我们消耗了所有输入数据而没有填满输出缓冲区。在
 * 这种情况下，当前输入数据的压缩表示将不会
 * 被发送到下一个bbsink，直到对该函数的后续调用，
 * 或者甚至可能要等到调用bbsink_gzip_end_archive()时。
 */
static void fc_bbsink_gzip_archive_contents(bbsink *fc_sink, size_t fc_len)
{
	bbsink_gzip *fc_mysink = (bbsink_gzip *) fc_sink;
	z_stream   *fc_zs = &fc_mysink->zstream;

	/* 从输入缓冲区压缩数据。 */
	fc_zs->next_in = (uint8 *) fc_mysink->base.bbs_buffer;
	fc_zs->avail_in = fc_len;

	while (fc_zs->avail_in > 0)
	{
		int			fc_res;

		/* 将输出数据写入输出缓冲区的未使用部分。 */
		Assert(fc_mysink->bytes_written < fc_mysink->base.bbs_next->bbs_buffer_length);
		fc_zs->next_out = (uint8 *)
			fc_mysink->base.bbs_next->bbs_buffer + fc_mysink->bytes_written;
		fc_zs->avail_out =
			fc_mysink->base.bbs_next->bbs_buffer_length - fc_mysink->bytes_written;

		/*
		 * 尝试压缩。请注意，这将根据消耗了多少输入数据
		 * 更新zs->next_in和zs->avail_in，并根据产生了多少输出字节
		 * 更新zs->next_out和zs->avail_out。
		 *
		 * 根据zlib文档，Z_STREAM_ERROR应仅在我们发生了编程错误，
		 * 或者如果例如发生了内存损坏时出现；我们在这里使用elog()
		 * 而不是Assert()出于过度谨慎的原因。
		 */
		fc_res = deflate(fc_zs, Z_NO_FLUSH);
		if (fc_res == Z_STREAM_ERROR)
			elog(ERROR, "could not compress data: %s", fc_zs->msg);

		/* 更新我们对已写字节数的概念。 */
		fc_mysink->bytes_written =
			fc_mysink->base.bbs_next->bbs_buffer_length - fc_zs->avail_out;

		/*
		 * 如果输出缓冲区已满，是时候让下一个接收器处理内容了。
		 */
		if (fc_mysink->bytes_written >= fc_mysink->base.bbs_next->bbs_buffer_length)
		{
			bbsink_archive_contents(fc_sink->bbs_next, fc_mysink->bytes_written);
			fc_mysink->bytes_written = 0;
		}
	}
}

/*
 * zlib的内部缓冲区中可能有一些数据；我们需要将
 * 其刷新并转发到后续的接收器作为归档内容。
 *
 * 然后我们可以结束该归档的处理。
 */
static void fc_bbsink_gzip_end_archive(bbsink *fc_sink)
{
	bbsink_gzip *fc_mysink = (bbsink_gzip *) fc_sink;
	z_stream   *fc_zs = &fc_mysink->zstream;

	/* 没有更多可用的数据。 */
	fc_zs->next_in = (uint8 *) fc_mysink->base.bbs_buffer;
	fc_zs->avail_in = 0;

	while (1)
	{
		int			fc_res;

		/* 将输出数据写入输出缓冲区的未使用部分。 */
		Assert(fc_mysink->bytes_written < fc_mysink->base.bbs_next->bbs_buffer_length);
		fc_zs->next_out = (uint8 *)
			fc_mysink->base.bbs_next->bbs_buffer + fc_mysink->bytes_written;
		fc_zs->avail_out =
			fc_mysink->base.bbs_next->bbs_buffer_length - fc_mysink->bytes_written;

		/*
		 * 与bbsink_gzip_archive_contents相同，但因为没有
		 * 更多输入而传递Z_FINISH。
		 */
		fc_res = deflate(fc_zs, Z_FINISH);
		if (fc_res == Z_STREAM_ERROR)
			elog(ERROR, "could not compress data: %s", fc_zs->msg);

		/* 更新我们对已写字节数的概念。 */
		fc_mysink->bytes_written =
			fc_mysink->base.bbs_next->bbs_buffer_length - fc_zs->avail_out;

		/*
		 * 显然我们在输出缓冲区中没有数据，且deflate()无法
		 * 添加任何数据。我们必须完成了。
		 */
		if (fc_mysink->bytes_written == 0)
			break;

		/* 发送我们已累积的输出字节。 */
		bbsink_archive_contents(fc_sink->bbs_next, fc_mysink->bytes_written);
		fc_mysink->bytes_written = 0;
	}

	/* 还必须传递该归档已结束的信息。 */
	bbsink_forward_end_archive(fc_sink);
}

/*
 * 清单内容未压缩，但我们确实需要将其复制到
 * 后续接收器的缓冲区中，因为我们有自己的缓冲区。
 */
static void fc_bbsink_gzip_manifest_contents(bbsink *fc_sink, size_t fc_len)
{
	memcpy(fc_sink->bbs_next->bbs_buffer, fc_sink->bbs_buffer, fc_len);
	bbsink_manifest_contents(fc_sink->bbs_next, fc_len);
}

/*
 * 包装函数以调整palloc的签名，以匹配libz
 * 的期望。
 */
static void * fc_gzip_palloc(void *fc_opaque, unsigned fc_items, unsigned fc_size)
{
	return palloc(fc_items * fc_size);
}

/*
 * 包装函数以调整pfree的签名，以匹配libz
 * 的期望。
 */
static void fc_gzip_pfree(void *fc_opaque, void *fc_address)
{
	pfree(fc_address);
}

#endif
